﻿using Apewer.Network;
using Apewer.Source;
using System;
using System.Net;
using System.Reflection;
using static Apewer.Web.ApiUtility;

namespace Apewer.Web
{

    internal class ApiProcessor
    {

        private ApiContext _context = null;

        internal ApiProcessor(ApiContext context) => _context = context ?? throw new ArgumentNullException(nameof(context));

        #region prepare

        /// <summary>执行处理程序，返回错误信息。</summary>
        public void Run()
        {
            var url = null as Uri;
            var method = HttpMethod.NULL;
            var response = null as ApiResponse;

            try
            {
                // 检查执行的前提条件，获取 Method 和 URL。
                var check = Check(ref method, ref url);
                if (!string.IsNullOrEmpty(check))
                {
                    Logger.Internals.Error(typeof(ApiInvoker), check);
                    return;
                }

                // 准备请求模型。
                var request = GetRequest(_context.Provider, _context.Options, method, url);
                _context.Request = request;

                // 准备响应模型。
                response = new ApiResponse();
                response.Random = request.Random;
                response.Application = request.Application;
                response.Function = request.Function;
                _context.Response = response;

                // 调用 API。
                Invoke();
            }
            catch (Exception ex)
            {
                var message = ex.Message();
                Logger.Internals.Error(typeof(ApiInvoker), message);
            }
            finally
            {
                // 输出。
                if (response != null)
                {
                    try
                    {
                        response.Duration = Duration(_context.Beginning);
                        Output(_context.Provider, _context.Options, response, null, method);
                    }
                    catch { }
                    finally
                    {
                        RuntimeUtility.Dispose(response.Model);
                    }
                }
            }
        }

        static string Duration(DateTime beginning)
        {
            var span = DateTime.Now - beginning;
            var ms = span.TotalMilliseconds;
            if (ms < 1000) return Math.Round(ms, 0).ToString() + "ms";
            if (ms < 10000) return Math.Round(ms / 1000, 2).ToString() + "s";
            if (ms < 60000) return Math.Round(ms / 1000, 1).ToString() + "s";
            return Math.Round(ms / 1000, 0).ToString() + "s";
        }

        string Check(ref HttpMethod method, ref Uri url)
        {
            // 服务程序检查。
            var check = _context.Provider.PreInvoke();
            if (!string.IsNullOrEmpty(check)) return check;

            // URL
            url = _context.Provider.GetUrl();
            if (url == null) return "URL 无效。";

            method = _context.Provider.GetMethod();
            if (method == HttpMethod.NULL) return "HTTP 方法无效。";
            if (method == HttpMethod.OPTIONS) return null;

            // favicon.ico
            var lowerPath = TextUtility.AssureStarts(TextUtility.Lower(url.AbsolutePath), "/");
            if (!_context.Options.AllowFavIcon)
            {
                if (lowerPath.StartsWith("/favicon.ico"))
                {
                    Output(_context.Provider, _context.Options, null, null, null);
                    return "已取消对 favicon.ico 的请求。";
                }
            }

            // robots.txt
            if (!_context.Options.AllowRobots)
            {
                if (lowerPath.StartsWith("/robots.txt"))
                {
                    const string text = "User-agent: *\nDisallow: / \n";
                    Output(_context.Provider, _context.Options, null, "text/plain", TextUtility.Bytes(text));
                    return "已取消对 robots.txt 的请求。";
                }
            }

            return null;
        }

        // 寻找入口。
        void Invoke()
        {
            // 路由
            if (_context.Options.UseRoute)
            {
                var path = _context?.Request?.Url?.AbsolutePath;
                path = path.TrimEnd('/');
                var action = _context.Entries.GetAction(path);
                if (action != null)
                {
                    _context.ApiAction = action;
                    Invoke(action);
                    _context.Response.Duration = Duration(_context.Beginning);
                    return;
                }
            }

            // 反射
            if (_context.Options.UseReflection)
            {
                var appName = _context.Request.Application;
                var application = _context.Entries.GetApplication(appName);
                Invoke(application);
                _context.Response.Duration = Duration(_context.Beginning);
                return;
            }

            // 未匹配到
            _context.Response.Duration = Duration(_context.Beginning);
            _context.Response.Model = new ApiStatusModel(404);
        }

        #endregion

        #region common

        // 创建控制器实例
        static ApiController CreateController(Type type, ApiRequest request, ApiResponse response, ApiOptions options)
        {
            var controller = (ApiController)Activator.CreateInstance(type);
            ApiUtility.SetProperties(controller, request, response, options);
            return controller;
        }

        static void Invoke(ApiContext context, MethodInfo method, ApiParameter[] parameters)
        {
            context.MethodInfo = method;

            // 调用。
            var parametersValue = ReadParameters(context.Request, parameters);
            var controller = context.Controller;
            var returnValue = method.Invoke(controller, parametersValue);

            // 程序要求停止输出。
            var response = context.Response;
            if (response.StopReturn) return;

            // 已经有了返回模型。
            if (response.Model != null) return;

            // 没有返回类型。
            var returnType = method.ReturnType;
            if (returnType == null) return;

            // 已明确字符串类型。
            if (returnType.Equals(typeof(string)))
            {
                var textValue = returnValue as string;
                var textRenderer = context.Options.TextRenderer;
                if (textRenderer != null)
                {
                    textRenderer.Invoke(context, textValue);
                    return;
                }

                // 默认视为提示错误
                if (!string.IsNullOrEmpty(textValue)) response.Error(textValue);
                return;
            }

            // 已明确 Exception 类型，视为提示错误。
            if (returnValue is Exception)
            {
                ApiUtility.Exception(response, returnValue as Exception);
                return;
            }

            // 已明确 Json 类型。
            if (returnValue is Json json)
            {
                var renderer = context.Options.JsonRenderer;
                if (renderer != null)
                {
                    renderer.Invoke(context, json);
                    return;
                }

                // 默认设置到 data 属性。
                response.Data = json;
                return;
            }

            // 已明确 Model 类型。
            if (returnValue is IApiModel model)
            {
                response.Model = model;
                return;
            }

            // 已明确 Result 类型。
            if (returnValue is IActionResult result)
            {
                response.Model = result;
                return;
            }

            // 类型未知，尝试 ToJson 方法。
            if (returnValue is IToJson toJson)
            {
                var tojson = toJson.ToJson();

                var renderer = context.Options.JsonRenderer;
                if (renderer != null)
                {
                    renderer.Invoke(context, tojson);
                    return;
                }
                response.Data = tojson;
                return;
            }

            // 未知返回类型，尝试使用默认渲染器。
            var defaultRenderer = context.Options.DefaultRenderer;
            if (defaultRenderer != null) defaultRenderer.Invoke(context, returnValue);
        }

        #endregion

        #region route

        // 执行 Action。
        void Invoke(ApiAction action)
        {
            var controller = null as ApiController;
            try
            {
                // 准备控制器。
                controller = CreateController(action.Type, _context.Request, _context.Response, _context.Options);

                // 准备参数。
                var parameters = action.Parameters;
                var values = ReadParameters(_context.Request, parameters);

                // 调用。
                _context.Controller = controller;
                Invoke(_context, action.MethodInfo, action.Parameters);
            }
            catch (Exception ex)
            {
                if (ex.InnerException != null) ex = ex.InnerException;
                ApiUtility.Exception(_context.Response, ex, _context.Options.WithException);

                var catcher = _context.Invoker.Catcher;
                if (catcher != null)
                {
                    try
                    {
                        var apiCatch = new ApiCatch(_context, ex);
                        catcher.Invoke(apiCatch);
                    }
                    catch { }
                }
            }
            finally
            {
                RuntimeUtility.Dispose(controller);
            }
        }

        #endregion

        #region reflection

        // 创建控制器。
        void Invoke(ApiApplication application)
        {
            var options = _context.Options;
            var entries = _context.Entries;
            var request = _context.Request;
            var response = _context.Response;

            // Application 无效，尝试默认控制器和枚举。
            if (application == null)
            {
                var @default = options.Default;
                if (@default == null)
                {
                    // 没有指定默认控制器，尝试枚举。
                    response.Status = "notfound";
                    response.Message = "Not Found";
                    if (options.AllowEnumerate) response.Data = Enumerate(entries.Applications, options);
                    return;
                }
                else
                {
                    // 创建默认控制器。
                    var controller = null as ApiController;
                    try
                    {
                        controller = CreateController(@default, request, response, options);
                        Invoke(controller, application, null, options, request, response);
                    }
                    catch (Exception ex)
                    {
                        ApiUtility.Exception(response, ex.InnerException ?? ex);
                    }
                    finally
                    {
                        RuntimeUtility.Dispose(controller);
                    }
                }
            }
            else
            {
                // 创建控制器时候会填充 Controller.Request 属性，可能导致 Request.Function 被篡改，所以在创建之前获取 Function。
                var function = application.GetFunction(request.Function);
                var controller = null as ApiController;
                try
                {
                    controller = CreateController(application.Type, request, response, options);
                    Invoke(controller, application, function, options, request, response);
                }
                catch (Exception ex)
                {
                    ApiUtility.Exception(response, ex.InnerException ?? ex);
                }
                finally
                {
                    RuntimeUtility.Dispose(controller);
                }
            }
        }

        // 调用 Function。
        void Invoke(ApiController controller, ApiApplication application, ApiFunction function, ApiOptions options, ApiRequest request, ApiResponse response)
        {
            try
            {
                // 控制器初始化。
                var initializer = ApiUtility.GetInitialier(controller);
                var match = initializer == null ? true : initializer.Invoke(controller);
                if (!match) return;
                if (application.Independent) return;

                if (function != null)
                {
                    // 调用 API，获取返回值。
                    _context.Controller = controller;
                    Invoke(_context, function.Method, function.Parameters);
                }
                else
                {
                    // 未匹配到 Function，尝试 Default。
                    var @default = ApiUtility.GetDefault(controller);
                    if (@default != null)
                    {
                        @default.Invoke(controller);
                        return;
                    }

                    // 没有执行任何 Function，尝试枚举。
                    response.Status = "notfound";
                    if (application.Hidden)
                    {
                        response.Message = "Not Found";
                    }
                    else
                    {
                        response.Message = "Not Found";
                        if (options.AllowEnumerate) response.Data = Enumerate(application.Functions, options);
                    }
                }
            }
            catch (Exception ex)
            {
                if (ex.InnerException != null) ex = ex.InnerException;
                ApiUtility.Exception(_context.Response, ex, _context.Options.WithException);

                var catcher = _context.Invoker.Catcher;
                if (catcher != null)
                {
                    try
                    {
                        var apiCatch = new ApiCatch(_context, ex);
                        catcher.Invoke(apiCatch);
                    }
                    catch { }
                }
            }
        }

        #endregion

        #region static

        internal static ApiRequest GetRequest(ApiProvider provider, ApiOptions options, HttpMethod method, Uri url)
        {
            // 创建数据对象。
            var request = new ApiRequest();

            // Http Method。
            request.Method = method;

            // 基本信息。
            var ip = provider.GetClientIP();
            var headers = provider.GetHeaders() ?? new HttpHeaders();
            request.Headers = headers;
            request.IP = ip;
            request.Url = url;
            request.Referrer = provider.GetReferrer();
            request.Parameters = ApiUtility.Parameters(url.Query);

            // Headers。
            request.UserAgent = ApiUtility.UserAgent(headers);
            request.Cookies = ParseCookies(headers) ?? new CookieCollection();

            // 匹配 API。
            var application = null as string;
            var function = null as string;
            var random = null as string;
            var ticket = null as string;
            var session = null as string;
            var page = null as string;

            // 解析 POST 请求。
            switch (request.Method)
            {
                case HttpMethod.PATCH:
                case HttpMethod.POST:
                case HttpMethod.PUT:
                    var preRead = provider.PreRead();
                    if (string.IsNullOrEmpty(preRead))
                    {
                        var post = null as byte[];
                        var length = 0L;
                        var max = options.MaxRequestBody;
                        if (max == 0) post = new byte[0];
                        else if (max < 0) post = provider.RequestBody().Read();
                        else
                        {
                            length = provider.GetContentLength();
                            if (length <= max) post = provider.RequestBody().Read();
                        }

                        length = post == null ? 0 : post.Length;
                        if (length > 1)
                        {
                            request.PostData = post;
                            if (length < 104857600)
                            {
                                var text = TextUtility.FromBytes(post);
                                request.PostText = text;

                                // 尝试解析 Json，首尾必须是“{}”或“[]”。
                                var first = post[0];
                                var last = post[length - 1];
                                if ((first == 123 && last == 125) || (first == 91 && last == 93))
                                {
                                    var json = Json.From(text);
                                    if (json != null && json.IsObject)
                                    {
                                        application = json["application"];
                                        function = json["function"];
                                        random = json["random"];
                                        ticket = json["ticket"];
                                        session = json["session"];
                                        page = json["page"];

                                        var data = json.GetProperty("data");
                                        request.PostJson = json;
                                        request.Data = data ?? Json.NewObject();
                                    }
                                }

                                // 尝试解析 Form，需要 application/x-www-form-urlencoded
                                var contentType = headers.GetValue("Content-Type") ?? "";
                                if (contentType.Contains("urlencoded")) request.Form = ApiUtility.Parameters(text);
                            }
                        }
                    }
                    break;
            }

            // 解析 URL 参数。
            // URL 参数的优先级应高于 URL 路径，以避免反向代理产生的路径问题。
            var urlParameters = ApiUtility.Parameters(request.Url.Query);
            if (string.IsNullOrEmpty(application)) application = urlParameters.GetValue("application");
            if (string.IsNullOrEmpty(function)) function = urlParameters.GetValue("function");
            if (string.IsNullOrEmpty(random)) random = urlParameters.GetValue("random");
            if (string.IsNullOrEmpty(ticket)) ticket = urlParameters.GetValue("ticket");
            if (string.IsNullOrEmpty(session)) session = urlParameters.GetValue("session");
            if (string.IsNullOrEmpty(page)) page = urlParameters.GetValue("page");

            // 从 Cookie 中获取 Ticket。
            var cookies = request.Cookies;
            if (string.IsNullOrEmpty(ticket)) ticket = cookies.GetValue("ticket");

            // 最后检查 URL 路径。
            var paths = (request.Url.AbsolutePath ?? "").Split('/');
            if (string.IsNullOrEmpty(application) && paths.Length >= 2) application = TextUtility.DecodeUrl(paths[1]);
            if (string.IsNullOrEmpty(function) && paths.Length >= 3) function = TextUtility.DecodeUrl(paths[2]);

            // 修正内容。
            application = TextUtility.Trim(application);
            function = TextUtility.Trim(function);
            random = TextUtility.Trim(random);
            ticket = TextUtility.Trim(ticket);
            session = TextUtility.Trim(session);
            page = TextUtility.Trim(page);

            // 设置请求：回传。
            request.Application = application;
            request.Function = function;
            request.Random = random;

            // 设置请求：不回传。
            request.Ticket = ticket;
            request.Session = session;
            request.Page = page;

            return request;
        }

        static StringPairs PrepareHeaders(ApiOptions options, ApiResponse response, ApiRequest request = null)
        {
            var merged = new StringPairs();
            if (options != null)
            {
                // 跨域访问。
                if (options.WithAccessControl)
                {
                    merged.Add("Access-Control-Allow-Headers", "Content-Type");
                    merged.Add("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
                    merged.Add("Access-Control-Allow-Origin", "*");

                    var maxage = options.AccessControlMaxAge;
                    if (maxage > 0) merged.Add("Access-Control-Max-Age", maxage.ToString());

                    if (request != null && request.Headers != null)
                    {
                        var @private = request.Headers.GetValue("Access-Control-Request-Private-Network");
                        if (NumberUtility.Boolean(@private)) merged.Add("Access-Control-Allow-Private-Network", "true");
                    }
                }

                // Content-Type 检查。
                if (options.WithContentTypeOptions || options.Default != null)
                {
                    merged.Add("X-Content-Type-Options", "nosniff");
                }

                // 用于客户端，当前页面使用 HTTPS 时，将资源升级为 HTTPS。
                if (options.UpgradeHttps)
                {
                    merged.Add("Content-Security-Policy", "upgrade-insecure-requests");
                }

                // 包含 API 的处理时间。
                if (options.WithDuration && response != null)
                {
                    if (response.Duration.NotEmpty()) merged.Add("Duration", response.Duration);
                }
            }
            if (response != null)
            {
                // Cookies。
                var setCookies = SetCookie(response.Cookies);
                if (setCookies != null)
                {
                    foreach (var value in setCookies) merged.Add("Set-Cookie", value);
                }

                // 自定义头。
                var headers = response.Headers;
                if (headers != null)
                {
                    foreach (var header in headers)
                    {
                        var key = TextUtility.Trim(header.Name);
                        if (string.IsNullOrEmpty(key)) continue;
                        var value = header.Value;
                        if (string.IsNullOrEmpty(value)) continue;
                        merged.Add(key, value);
                    }
                }
            }
            return merged;
        }

        internal void Output(ApiProvider provider, ApiOptions options, ApiResponse response, string type, byte[] bytes)
        {
            var preWrite = provider.PreWrite();
            if (!string.IsNullOrEmpty(preWrite)) return;

            if (response != null)
            {
                var responsePreOutput = response.PreOutput;
                if (responsePreOutput != null)
                {
                    var @continue = responsePreOutput.Invoke(_context);
                    if (!@continue) return;
                }
            }

            var invokerPreOutput = _context.Invoker.PreOutput;
            if (invokerPreOutput != null)
            {
                var @continue = invokerPreOutput.Invoke(_context);
                if (!@continue) return;
            }

            var optionsPreOutput = _context.Options.PreOutput;
            if (optionsPreOutput != null)
            {
                var @continue = optionsPreOutput.Invoke(_context);
                if (!@continue) return;
            }

            var headers = PrepareHeaders(options, response);
            foreach (var header in headers) provider.SetHeader(header.Key, header.Value);

            provider.SetCache(0);
            provider.SetContentType(string.IsNullOrEmpty(type) ? "application/octet-stream" : type);

            var length = bytes == null ? 0 : bytes.Length;
            provider.SetContentLength(length);
            if (length > 0) provider.ResponseBody().Write(bytes, 0, bytes.Length);
            provider.Sent();
        }

        internal void Output(ApiProvider provider, ApiOptions options, ApiResponse response, ApiRequest request, HttpMethod method)
        {
            var preWrite = provider.PreWrite();
            if (!string.IsNullOrEmpty(preWrite)) return;

            if (response != null)
            {
                var responsePreOutput = response.PreOutput;
                if (responsePreOutput != null)
                {
                    var @continue = responsePreOutput.Invoke(_context);
                    if (!@continue) return;
                }
            }

            var invokerPreOutput = _context.Invoker.PreOutput;
            if (invokerPreOutput != null)
            {
                var @continue = invokerPreOutput.Invoke(_context);
                if (!@continue) return;
            }

            var optionsPreOutput = _context.Options.PreOutput;
            if (optionsPreOutput != null)
            {
                var @continue = optionsPreOutput.Invoke(_context);
                if (!@continue) return;
            }

            // 设置头。
            var headers = PrepareHeaders(options, response, request);
            foreach (var header in headers) provider.SetHeader(header.Key, header.Value);

            // 自定义模型
            var model = response.Model as IApiModel;
            var result = response.Model as IActionResult;
            if (model != null)
            {
                try
                {
                    model.Output(_context);
                }
                catch (Exception ex)
                {
                    Logger.Internals.Exception(ex, model);
                }
                RuntimeUtility.Dispose(model);
                return;
            }
            else if (result != null)
            {
                try
                {
                    result.ExecuteResult(_context);
                }
                catch (Exception ex)
                {
                    Logger.Internals.Exception(ex, result);
                }
                RuntimeUtility.Dispose(result);
                return;
            }

            var text = ApiUtility.ToJson(response, options);
            var bytes = TextUtility.Bytes(text);
            provider.SetCache(0);
            provider.SetContentType("text/json; charset=utf-8");
            provider.SetContentLength(bytes.Length);
            var stream = provider.ResponseBody();
            if (stream != null && stream.CanWrite) stream.Write(bytes, 0, bytes.Length);
            provider.Sent();
        }

        #endregion

    }

}
